package org.longkey.portal
{
	import mx.collections.IList;
	import mx.core.mx_internal;

	use namespace mx_internal;
	
	public class PHPSerializer extends Object
	{
		private static var p:int = 0;

		mx_internal static function stringCLenght(data:String, from:uint = 0, len:uint = 0):int
		{
			var i:uint;
			var j:uint = len;
			var startIndex:uint = from + 4 + len.toString().length;
			for (i = 0; i < j; i++){
				if (data.charCodeAt(i+startIndex) > 128)
				{
					j = j - 1
				}
			}
			return j;
		}
		
		mx_internal static function stringLength(data:String):uint
		{
			var code:int   = 0
			var result:int = 0
			var slen:int   = data.length;
			while(slen){
				slen = slen - 1
				try
				{
					code = data.charCodeAt(slen)
				} catch(e:Error){
					code = 65536
				}
				if(code < 128){
					result = result + 1
				} else if(code < 2048){
					result = result + 2
				} else if(code < 65536){
					result = result + 3
				} else {
					result = result + 4
				}
			}
			return result
		}
		
		
		
		public static function serialize(o:*):String{
			PHPSerializer.p = 0;
			var sb:Array = [];
			serializeObject(sb, o);
			return sb.join('');
		}
		
		mx_internal static function serializeNull(sb:Array):void{
			sb[p++] = 'N;';
		}
		mx_internal static function serializeBoolean(sb:Array, b:Boolean):void {
			sb[p++] = 'b:' + uint(b) + ";";
		}
		mx_internal static function serializeInteger(sb:Array, i:int):void {
			sb[p++] = 'i:' + i.toString() + ';';
		}
		mx_internal static function serializeDouble(sb:Array, d:Number):void {
			sb[p++] = 'd:' + d.toString() + ';';
		}
		mx_internal static function serializeString(sb:Array, s:String):void {
			sb[p++] = 's:' + stringLength(s) + ':"';
			sb[p++] = s;
			sb[p++] = '";';
		}
		mx_internal static function serializeDate(sb:Array, dt:Date):void {
			sb[p++] = 'd:' + dt.getTime() + ';';
		}
		mx_internal static function serializeArray(sb:Array, a:Array):void {
			sb[p++] = 'a:';
			sb[p++] = a.length;
			sb[p++] = ':{';
			for(var k:int = 0; k < a.length; k++){
				serializeInteger(sb,k as int);
				serializeObject(sb, a[k]);
			}
			sb[p++] = '}';
		}
		mx_internal static function serializeObject(sb:Array, o:Object):void {
			if(o == null || o is Function){
				serializeNull(sb);
			}
			else if(o is Boolean){
				serializeBoolean(sb, o as Boolean);
			} else if(o is int){
				serializeInteger(sb, o as int);
			} else if(o is Number){
				serializeDouble(sb, o as Number);
			} else if(o is String){
				serializeString(sb, o as String);
			} else if(o is Date){
				serializeDate(sb, o as Date);
			} else if(o is Array){
				serializeArray(sb, o as Array); 
			} else if(o is IList){
				serializeArray(sb, IList(o).toArray());
			} else {
				sb[p++] = 'O:8:"stdClass":';
				var pt:int = p;
				sb[p++] = 0;
				sb[p++] = ':{';
				for(var k:Object in o){
					if(k is Function || o[k] is Function)
						continue;
					serializeObject(sb, k);
					serializeObject(sb, o[k]);
					sb[pt]++;
				}
				sb[p++] = '}';
			}
		}
		
		public static function unserialize(ss:String):Object{
			p = 0;
			return unserializeObject(ss);
		}
		mx_internal static function unserializeNull():Object {
			p++;
			return null;
		}
		mx_internal static function unserializeBoolean(ss:String):Boolean {
			p++;
			var b:Boolean = (ss.charAt(p++) == '1');
			p++;
			return b;
		}
		mx_internal static function unserializeInteger(ss:String):int {
			p++;
			var i:int = parseInt(ss.substring(p, p = ss.indexOf(';', p)));
			p++;
			return i;
		}
		mx_internal static function unserializeDouble(ss:String):Number {
			p++;
			var d:String = ss.substring(p, p = ss.indexOf(';', p));
			var num:Number;
			switch (d) {
				case 'NAN':
					num = NaN;
					break;
				case 'INF':
					num = Number.POSITIVE_INFINITY;
					break;
				case '-INF':
					num = Number.NEGATIVE_INFINITY;
					break;
				default:
					num = parseFloat(d);
			}
			p++;
			return num;
		}
		mx_internal static function unserializeString(ss:String):String {
			p++;
			var l:int = parseInt(ss.substring(p, p = ss.indexOf(':', p)));
			p += 2;
			var s:String = ss.substring(p, p += l);
			p += 2;
			return s;
		}
		mx_internal static function unserializeEscapedString(ss:String, len:int):String {
			p++;
			var l:int = parseInt(ss.substring(p, p = ss.indexOf(':', p)));
			p += 2;
			var sb:Array = new Array(l);
			for (var i:int = 0; i < l; i++) {
				if ((sb[i] = ss.charAt(p++)) == '\\') {
					sb[i] = String.fromCharCode(parseInt(ss.substring(p,p += len), 16));
				}
			}
			p += 2;
			return sb.join('');
		}
		mx_internal static function unserializeArray(ss:String):Array {
			p++;
			var n:int = parseInt(ss.substring(p, p = ss.indexOf(':', p)));
			p += 2;
			var k:Object;
			var a:Array = [];
			for (var i:int = 0; i < n; i++) {
				switch (ss.charAt(p++)) {
					case 'i':
						k = unserializeInteger(ss);
						break;
					case 's':
						k = unserializeString(ss);
						break;
					case 'S':
						k = unserializeEscapedString(ss,2);
						break;
					case 'U':
						k = unserializeEscapedString(ss,4);
						break;
					default:
						return a;
				}
				a[k] = unserializeObject(ss);
			}
			p++;
			return a;
		}
		mx_internal static function unserializeMap(ss:String):Object {
			p++;
			var n:int = parseInt(ss.substring(p, p = ss.indexOf(':', p)));
			p += 2;
			var m:Object = {};
			for (var i:int = 0; i < n; i++) {
				var k:String;
				switch (ss.charAt(p++)) {
					case 's':
						k = unserializeString(ss);
						break;
					case 'S':
						k = unserializeEscapedString(ss,2);
						break;
					case 'U':
						k = unserializeEscapedString(ss,4);
						break;
					default:
						return false;
				}
				if (k.charAt(0) == '\0') {
					k = k.substring(k.indexOf('\0', 1) + 1, k.length);
				}
				m[k] = unserializeObject(ss);
			}
			p++;
			return m;
		}
		mx_internal static function unserializeObject(ss:String):Object {
			switch (ss.charAt(p++)) {
				case 'N':
					return unserializeNull();
				case 'b':
					return unserializeBoolean(ss);
				case 'i':
					return unserializeInteger(ss);
				case 'd':
					return unserializeDouble(ss);
				case 's':
					return unserializeString(ss);
				case 'S':
					return unserializeEscapedString(ss,2);
				case 'U':
					return unserializeEscapedString(ss,4);
				case 'a':
					return unserializeArray(ss);
				case 'r':
				case 'm':
				case 'O':
				case 'C':
				case 'R':
					return unserializeMap(ss);
				default:
					return false;
			}
		}			
	}
}